#pragma once

enum eOpcodeType
{
	OPCODE_TYPE_UNUSED,
	OPCODE_TYPE_COMMAND,
	OPCODE_TYPE_OPERATION,
	OPCODE_TYPE_UNKNOWN
};

class COpcode
{
	friend class COpcodes;

protected:
	eOpcodeType					m_bType;
	DWORD						m_dwId;
	int							m_nIndex;
	int							m_nParseFunction;
	bool						m_bIsConditional	: 1;
	bool						m_bIsNOP			: 1;

	COpcode::COpcode(DWORD id, eOpcodeType bType) : m_bType(bType),
		m_dwId(id), m_nIndex(0), m_nParseFunction(0),
		m_bIsConditional(false), m_bIsNOP(false)
	{
	}
	COpcode::~COpcode()
	{
	}

public:
	virtual class CCommand				*	AsCommand()						{return nullptr;}
	inline int								GetParseFunc() const			{return m_nParseFunction ? (m_nParseFunction - 1) : (-1);}
	inline void								SetParseFunc(int id)			{m_nParseFunction = id + 1;}
	inline void								SetIsConditional(bool b)		{m_bIsConditional = b;}
	inline void								SetIsNOP(bool b)				{m_bIsNOP = b;}

	inline bool								IsConditional() const			{return m_bIsConditional;}
	inline bool								IsNOP() const					{return m_bIsNOP;}

	inline eOpcodeType						GetType() const					{return m_bType;}
	inline DWORD							GetId() const					{return m_dwId;}

	template<class T> inline T * Get()					{return nullptr;}
	template<class T> inline const T * Get() const		{return nullptr;}
	template<> inline CCommand * Get<CCommand>()
	{
		return m_bType == OPCODE_TYPE_COMMAND || m_bType == OPCODE_TYPE_UNKNOWN ? reinterpret_cast<CCommand*>(this) : nullptr;
	}
	template<> inline COperation * Get<COperation>()
	{
		return m_bType == OPCODE_TYPE_OPERATION || m_bType == OPCODE_TYPE_UNKNOWN ? reinterpret_cast<COperation*>(this) : nullptr;
	}
	template<> inline const CCommand * Get<const CCommand>() const
	{
		return m_bType == OPCODE_TYPE_COMMAND || m_bType == OPCODE_TYPE_UNKNOWN ? reinterpret_cast<const CCommand*>(this) : nullptr;
	}
	template<> inline const COperation * Get<const COperation>() const
	{
		return m_bType == OPCODE_TYPE_OPERATION || m_bType == OPCODE_TYPE_UNKNOWN ? reinterpret_cast<const COperation*>(this) : nullptr;
	}
};
class COpcodes
{
	friend class COpcode;
	static CList<COpcode>		s_pOpcodeList;
	static COpcode			*	s_pOpcodes[MAX_OPCODES];
	static COpcode			*	s_pParseOpcodes[SCRIPT_MAX_PARSED_OPCODES];
	static int					s_nNumOpcodes;

	COpcodes()
	{
		s_nNumOpcodes = 0;
	}

public:
	// Register opcode - hashes are automatically handled
	static void Register(COpcode * pOpcode)
	{
		if(GetIntNumBytes(pOpcode->GetId()) > 2)
		{
			s_pOpcodeList.Add(pOpcode->GetId(), pOpcode);
			++s_nNumOpcodes;
		}
		else if(!s_pOpcodes[pOpcode->GetId()])
		{
			s_pOpcodes[pOpcode->GetId()] = pOpcode;
			++s_nNumOpcodes;
		}
	}

	// Deregister opcode - hashes are automatically handled
	static void Deregister(COpcode * pOpcode)
	{
		if(GetIntNumBytes(pOpcode->GetId()) > 2)
		{
			s_pOpcodeList.Drop(pOpcode->GetId(), pOpcode);
			--s_nNumOpcodes;
		}
		else if(s_pOpcodes[pOpcode->GetId()])
		{
			s_pOpcodes[pOpcode->GetId()] = nullptr;
			--s_nNumOpcodes;
		}
	}

	// register for parsing with an index < SCRIPT_MAX_PARSED_OPCODES
	static inline void RegisterForParsing(int nId, COpcode * pOpcode)
	{
		if(!s_pParseOpcodes[nId]) s_pParseOpcodes[nId] = pOpcode;
	}
	/*static inline void DeregisterForParsing(int nId)
	{
		if(s_pParseOpcodes[nId]) s_pParseOpcodes[nId] = nullptr;
	}*/

	// use for opcodes, not hashes (works for hashes < 0x8000)
	static inline COpcode * Get(short opcode)
	{
		return opcode >= 0 ? s_pOpcodes[opcode] : nullptr;
	}
	
	// only use > 16bit opcodes for hashes with random order
	static inline COpcode * Get(DWORD hash)
	{
		return GetIntNumBytes(hash) > 2 ? s_pOpcodeList.Get(hash) : s_pOpcodes[hash];
	}

	// only for a parsed commands (pass parse index)
	static inline COpcode * GetForParsing(int nId)
	{
		return nId < SCRIPT_MAX_PARSED_OPCODES ? s_pParseOpcodes[nId] : nullptr;
	}
};